package util

import (
	"archive/zip"
	"io"
	"io/fs"
	"os"
	"path"
	"path/filepath"
	"strings"
)

// srcFile could be a single file or a directory
// func MyZip(zipPath string, paths ...string) error {
// 	zipfile, err := os.Create(destZip)
// 	if err != nil {
// 		return err
// 	}
// 	defer zipfile.Close()
// 	var spliter string
// 	if runtime.GOOS == "windows" {
// 		spliter = "\\"
// 	} else if runtime.GOOS == "linux" {
// 		spliter = "/"
// 	}
// 	archive := zip.NewWriter(zipfile)
// 	defer archive.Close()

// 	err = filepath.Walk(srcFile, func(fullname string, info os.FileInfo, err error) error {
// 		if err != nil {
// 			return err
// 		}

// 		header, err := zip.FileInfoHeader(info)
// 		if err != nil {
// 			return err
// 		}

// 		header.Name = strings.TrimPrefix(fullname, filepath.Dir(srcFile)+spliter)
// 		if info.IsDir() {
// 			header.Name += spliter
// 		} else {
// 			header.Method = zip.Deflate

// 		}

// 		writer, err := archive.CreateHeader(header)
// 		if err != nil {
// 			return err
// 		}

// 		if !info.IsDir() {
// 			file, err := os.Open(fullname)
// 			if err != nil {
// 				return err
// 			}
// 			defer file.Close()
// 			_, err = io.Copy(writer, file)
// 		}
// 		return err
// 	})

//		return err
//	}
// func Unzip(zipFile string, destDir string) error {
// 	zipReader, err := zip.OpenReader(zipFile)
// 	if err != nil {
// 		return err
// 	}
// 	defer zipReader.Close()

// 	for _, f := range zipReader.File {
// 		fpath := filepath.Join(destDir, f.Name)
// 		if f.FileInfo().IsDir() {
// 			if err = os.MkdirAll(fpath, os.ModePerm); err != nil {
// 				return err
// 			}
// 		} else {
// 			if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
// 				return err
// 			}

// 			inFile, err := f.Open()
// 			if err != nil {
// 				return err
// 			}
// 			defer inFile.Close()

// 			outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
// 			if err != nil {
// 				return err
// 			}
// 			defer outFile.Close()

// 			_, err = io.Copy(outFile, inFile)
// 			if err != nil {
// 				return err
// 			}
// 		}
// 	}
// 	return nil
// }

// Zip compresses the specified files or dirs to zip archive.
// If a path is a dir don't need to specify the trailing path separator.
// For example calling Zip("archive.zip", "dir", "csv/baz.csv") will get archive.zip and the content of which is
// baz.csv
// dir
// ├── bar.txt
// └── foo.txt
// Note that if a file is a symbolic link it will be skipped.
func Zip(zipPath string, paths ...string) error {
	// Create zip file and it's parent dir.
	if err := os.MkdirAll(filepath.Dir(zipPath), os.ModePerm); err != nil {
		return err
	}
	archive, err := os.Create(zipPath)
	if err != nil {
		return err
	}
	defer archive.Close()

	// New zip writer.
	zipWriter := zip.NewWriter(archive)
	defer zipWriter.Close()

	// Traverse the file or directory.
	for _, rootPath := range paths {
		// Remove the trailing path separator if path is a directory.
		rootPath = strings.TrimSuffix(rootPath, string(os.PathSeparator))

		// Visit all the files or directories in the tree.
		err = filepath.Walk(rootPath, walkFunc(rootPath, zipWriter))
		if err != nil {
			return err
		}
	}
	return nil
}

func walkFunc(rootPath string, zipWriter *zip.Writer) filepath.WalkFunc {
	return func(path string, info fs.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// If a file is a symbolic link it will be skipped.
		if info.Mode()&os.ModeSymlink != 0 {
			return nil
		}

		// Create a local file header.
		header, err := zip.FileInfoHeader(info)
		if err != nil {
			return err
		}

		// Set compression method.
		header.Method = zip.Deflate

		// Set relative path of a file as the header name.
		header.Name, err = filepath.Rel(filepath.Dir(rootPath), path)
		if err != nil {
			return err
		}
		if info.IsDir() {
			header.Name += string(os.PathSeparator)
		}

		// Create writer for the file header and save content of the file.
		headerWriter, err := zipWriter.CreateHeader(header)
		if err != nil {
			return err
		}
		if info.IsDir() {
			return nil
		}
		f, err := os.Open(path)
		if err != nil {
			return err
		}
		defer f.Close()
		_, err = io.Copy(headerWriter, f)
		return err
	}
}

// Unzip decompresses a zip file to specified directory.
// Note that the destination directory don't need to specify the trailing path separator.
// If the destination directory doesn't exist, it will be created automatically.
func Unzip(zipath, dir string) error {
	// Open zip file.
	reader, err := zip.OpenReader(zipath)
	if err != nil {
		return err
	}
	defer reader.Close()
	for _, file := range reader.File {
		if err := unzipFile(file, dir); err != nil {
			return err
		}
	}
	return nil
}

func unzipFile(file *zip.File, dir string) error {
	// Prevent path traversal vulnerability.
	// Such as if the file name is "../../../path/to/file.txt" which will be cleaned to "path/to/file.txt".
	name := strings.TrimPrefix(filepath.Join(string(filepath.Separator), file.Name), string(filepath.Separator))
	filePath := path.Join(dir, name)

	// Create the directory of file.
	if file.FileInfo().IsDir() {
		if err := os.MkdirAll(filePath, os.ModePerm); err != nil {
			return err
		}
		return nil
	}
	if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
		return err
	}

	// Open the file.
	r, err := file.Open()
	if err != nil {
		return err
	}
	defer r.Close()

	// Create the file.
	w, err := os.Create(filePath)
	if err != nil {
		return err
	}
	defer w.Close()

	// Save the decompressed file content.
	_, err = io.Copy(w, r)
	return err
}
